Conversation
Complete v1 implementation: - Fix 16 proto fidelity gaps across 46 model types - Add ContentCase/PayloadCase computed enums for all oneof types - Implement full ListTasks with pagination, sorting, filtering - Port REST API (MapHttpA2A) with 11 endpoints and SSE streaming - Add A2A-Version header negotiation to JSON-RPC processor - Fix A2AClient.Dispose, InMemoryTaskStore race conditions - Seal JsonRpcResponseResult/JsonRpcStreamedResult - Fix AgentCardSignature.Header type (object -> JsonElement) Backward compatibility: - Create A2A.V0_3 project (65 files, 255 tests) for compat layer - Both v1 and v0.3 coexist via separate NuGet packages Documentation: - Add docs/migration-guide-v1.md (12 sections, 21 breaking changes) Samples: - Update all agents with Version, skills - SpecComplianceAgent: cancel, continuation, history tracking Build: 0 errors, 0 warnings Tests: 986 unit tests passed TCK: 76 mandatory tests passed, 0 failed
- Remove unused A2AClient(HttpClient, string) constructor (Issue #1) - Add XML docs for DistributedCacheTaskStore limitations: ListTasksAsync returns empty on key-value caches, UpdateStatusAsync/AppendHistoryAsync have read-modify-write race conditions (Issues a2aproject#2, a2aproject#3) - Refactor A2AMethods.IsValidMethod for readability, reuse IsStreamingMethod/IsPushNotificationMethod helpers - Add TaskManager migration guide (docs/taskmanager-migration-guide.md) - Restore v1 test coverage (134 → 213 tests): - New tests for SendMessageResponse, StreamResponse, Message, Role, Part - A2AClient tests for ListTasks, CancelTask, push notification CRUD - AgentCard tests for SupportedInterfaces, SecuritySchemes - TaskManager test for GetExtendedAgentCard - Restore AspNetCore test coverage (40 → 71 tests): - ListTasks validation (pageSize, historyLength) - Push notification and ExtendedAgentCard error handling - Version negotiation (A2A-Version header: 0.3, 1.0, unsupported)
Security:
- Sanitize raw Exception.Message in 5 HTTP/JSON-RPC error responses to
prevent leaking internal details (stack traces, file paths, DB errors)
- Sanitize JsonException.Message in DeserializeAndValidate to avoid
exposing internal type/path details in InvalidParams errors
- A2AException.Message still exposed (intentional, app-defined errors)
Correctness:
- Add try/catch to JsonRpcStreamedResult SSE streaming; write best-effort
JSON-RPC error event on failure, catch OperationCanceledException
- Add error handling to A2AEventStreamResult SSE streaming alongside
existing OperationCanceledException catch (AOT-safe string literal)
- Return 400 Bad Request for invalid status query parameter in
ListTasksRestAsync instead of silently ignoring
Documentation:
- Add <remarks> on MapHttpA2A documenting /v1/ route prefix convention
and missing multi-tenant route support (/{tenant}/... variants)
…e target frameworks across all projects:\n- Libraries: net9.0;net8.0;netstandard2.0 → net10.0;net8.0\n- AspNetCore: net9.0;net8.0 → net10.0;net8.0\n- Tests: net8.0;net9.0 → net10.0;net8.0\n- Samples: net9.0 → net10.0\n\nConditionally exclude System.Linq.AsyncEnumerable and\nSystem.Net.ServerSentEvents for net10.0 (now in-box in BCL).\n\nRemove 8 polyfill files and all #if NET / #if NET8_0_OR_GREATER\nconditional compilation blocks no longer needed without\nnetstandard2.0.\n\nFix new .NET 10 analyzer warnings: CA1869 in AgentClient sample,\nCS8604 in AspNetCore unit tests."
…, and observability
BREAKING CHANGE: ITaskManager renamed to IA2ARequestHandler, TaskManager renamed to A2AServer
Server Architecture:
- A2AServer (formerly TaskManager): lifecycle orchestrator with context resolution,
terminal state guards, event persistence, history management, and try/finally
safety net preventing deadlocks when handlers throw or forget to close the queue
- IAgentHandler: easy-path agent contract with ExecuteAsync + default CancelAsync
- AgentContext: pre-resolved request context with TaskId, ContextId, UserText
- AgentEventQueue: Channel-backed event stream with typed Enqueue* methods
- TaskUpdater: lifecycle convenience helper for all 8 task states
- A2AServerOptions: AutoAppendHistory + AutoPersistEvents configuration
- Fix bounded channel deadlock by running handler concurrently with consumer
Observability:
- A2ADiagnostics: ActivitySource('A2A') + Meter('A2A') with 9 metric instruments
- A2AAspNetCoreDiagnostics: consolidated ActivitySource('A2A.AspNetCore'),
replacing 3 fragmented sources (A2A.Processor, A2A.HttpProcessor, A2A.Endpoint)
- Client spans on all 11 IA2AClient methods via 2 instrumented private helpers
- A2ACardResolver span for agent card discovery
- W3C Trace Context auto-propagated via HttpClient
DI Integration:
- AddA2AAgent<T>(): one-line service registration with AOT support
- MapA2A(path): DI-aware endpoint mapping resolving from container
Samples:
- All agents rewritten to IAgentHandler pattern (67-144 lines → 20-97 lines)
- Program.cs uses AddA2AAgent<T>() + MapA2A() DI pattern
- SemanticKernelAgent migrated to IAgentHandler + TaskUpdater
Tests: 302 pass (231 UnitTests + 71 AspNetCore) on net8.0 + net10.0
TCK: 76 mandatory tests pass
…ndler tracing - Rename taskManager → requestHandler across all AspNetCore processors and tests for consistency with IA2ARequestHandler interface name - Add Activity tracing + ILogger to all 12 REST handlers (REST.* span names) - Delete simplified (no-trace, no-log) exception wrappers — unified wrapper pattern - All 17 HTTP handlers now have Activity spans + error logging
- V0.3 CancelTaskAsync: re-fetch task after UpdateStatusAsync instead of returning pre-cancellation object (port of upstream a2aproject#283) - V1 MaterializeResponseAsync: re-fetch task from store after consuming all events to reflect final persisted state (status updates, artifacts) - Both throw if re-fetch returns null to surface store inconsistencies rather than silently returning stale data Critical for stores that don't mutate objects in-place (distributed caches, databases). Masked by InMemoryTaskStore's reference semantics.
- add IEventStore, ITaskEventStore, EventEnvelope, TaskProjection, InMemoryEventStore - implement spec-compliant SubscribeToTaskAsync with catch-up-then-live - simplify PersistEventAsync to single AppendAsync, fix artifact append semantics - remove ITaskStore, InMemoryTaskStore, DistributedCacheTaskStore, TaskStoreAdapter - add 35 new unit tests, pass all 76 TCK mandatory tests 🏗️ - Generated by Copilot
- add file-backed ITaskEventStore with per-task JSONL event logs - maintain materialized projection files for O(1) GetTaskAsync - add context index files for efficient ListTasksAsync filtering - make TaskStateExtensions.IsTerminal and TaskProjection public for external use 📁 - Generated by Copilot
- add self-contained demo with server start, task creation, stop, restart, verify - demonstrate ListTasksAsync by context filter works across restarts - add --store file and --data-dir options to AgentServer sample - make TaskStateExtensions public for external ITaskEventStore implementors 📁 - Generated by Copilot
- extract notification concern into ChannelEventNotifier and ChannelEventSubscriber - remove ~115 lines of duplicate subscriber boilerplate from InMemoryEventStore and FileEventStore - add GetTaskWithVersionAsync for atomic task+version reads (fixes TOCTOU race in subscribe) - add dedicated storage JSON options for FileEventStore JSONL safety - add 13 new tests for subscriber, notifier, and atomic version reads 🏗️ - Generated by Copilot
Summary of ChangesHello @rokonec, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces a preview implementation of the A2A v1.0 protocol specification, marking a significant upgrade to the SDK's core architecture. The changes involve a complete overhaul of the data models, client, and server components to align with the new protocol's serialization conventions and interaction patterns. A dedicated compatibility layer has been added to ease the transition for existing users, alongside extensive documentation and updated samples to demonstrate the new API usage. Highlights
Changelog
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces a significant and well-executed refactoring to implement version 1.0 of the A2A protocol SDK. The changes are extensive, including a new DI-based server-side API, an event sourcing pattern for task persistence, a v0.3 compatibility layer, and improved observability through tracing and metrics. The new architecture is a major improvement in terms of design and robustness. The code quality is high, and the inclusion of detailed migration guides is commendable. I've found one minor issue regarding specification compliance that I've commented on.
- replace IEventStore/ITaskEventStore (7 methods) with ITaskStore (4 CRUD methods) - add atomic per-task locking in ChannelEventNotifier for race-free subscribe - remove EventEnvelope, ChannelEventSubscriber, and all version/replay infrastructure - add 10 new tests: pagination, timestamp filter, concurrent access, race conditions - eliminate Task.Delay in subscribe tests with deterministic TaskCompletionSource 🏗️ - Generated by Copilot
…parameter - count task creation only when store has no existing task (currentTask is null) - rename @event to streamEvent in TaskProjection.Apply to avoid keyword escaping 🔧 - Generated by Copilot
…PersistEvents - rename PersistEventAsync to ApplyEventAsync to reflect read-mutate-save-notify semantics - remove AutoPersistEvents option (server cannot function without applying events) - simplify MaterializeResponseAsync and SendStreamingMessageAsync by removing conditional 🔧 - Generated by Copilot
- add Metadata property to CancelTaskRequest (spec proto field 3) - wire request.Metadata to AgentContext in CancelTaskAsync - add test verifying metadata passthrough to cancel handler 🔧 - Generated by Copilot
- add historyLength >= 0 validation in A2AServer.GetTaskAsync (covers both JSON-RPC and REST) - remove duplicate validation from A2AJsonRpcProcessor (server handles it) - update test to expect InvalidParams error for negative historyLength 🔧 - Generated by Copilot
- clarify that the property indicates streaming response mode, not request streaming - avoid terminology conflict with v1.1 streaming request support (per review feedback) 🔧 - Generated by Copilot
- align with Python SDK naming (RequestContext) - avoid overloading 'context' with A2A protocol contextId - 29 edits across 12 files via symbol rename 🔧 - Generated by Copilot
…creation - make RequestContext.ContextId nullable (string?) to expose client-provided vs generated - move GUID generation from ResolveContextAsync to TaskUpdater constructor - agents can now distinguish null (client didn't provide) from non-null (client or existing task) - update samples to use updater.ContextId for resolved value 🔧 - Generated by Copilot
…extId - rename AgentContext to RequestContext in DemoAgent - use updater.ContextId instead of context.ContextId for null-safety 🔧 - Generated by Copilot
…Id flag - spec requires agents MUST generate contextId when client doesn't provide one - restore GUID generation in ResolveContextAsync (was broken for message-only flows) - make ContextId non-null again (required string) for spec compliance - add ClientProvidedContextId bool so agents can distinguish client vs SDK-generated - revert TaskUpdater to accept non-null string contextId 🔧 - Generated by Copilot
- add MessageResponder with ReplyAsync(text) and ReplyAsync(parts) methods - auto-sets Role.Agent, MessageId, and ContextId on all replies - fixes spec compliance: agent response messages now always include contextId - update EchoAgent and FileStoreDemo to use MessageResponder - add 3 MessageResponder unit tests 🔧 - Generated by Copilot
…extId - rename AgentContext to RequestContext in DemoAgent - use updater.ContextId instead of context.ContextId for null-safety 🔧 - Generated by Copilot
…Id flag - spec requires agents MUST generate contextId when client doesn't provide one - restore GUID generation in ResolveContextAsync (was broken for message-only flows) - make ContextId non-null again (required string) for spec compliance - add ClientProvidedContextId bool so agents can distinguish client vs SDK-generated - revert TaskUpdater to accept non-null string contextId 🔧 - Generated by Copilot
- add MessageResponder with ReplyAsync(text) and ReplyAsync(parts) methods - auto-sets Role.Agent, MessageId, and ContextId on all replies - fixes spec compliance: agent response messages now always include contextId - update EchoAgent and FileStoreDemo to use MessageResponder - add 3 MessageResponder unit tests 🔧 - Generated by Copilot
A2A .NET SDK v1 Implementation
Summary
Complete implementation of the A2A v1 specification for the .NET SDK, including protocol models, server-side architecture redesign, simple CRUD task store, atomic subscribe, observability, and backward compatibility with v0.3.
241 files changed, 19,857 insertions, 8,770 deletions across 21 commits.
How to Review
This PR is large but logically layered. Recommended review order by commit:
d223872src/A2A/Models/, client, v0.3 compat3ba763c9e11c78A2AHttpProcessor.cs,A2AJsonRpcProcessor.cs0ebe07a.csprojfiles, removed polyfillsad6e513A2AServer.cs,IAgentHandler.cs, DI, observability2f8e41f9979994MaterializeResponseAsync,CancelTaskAsync03328b2..6cc23bba7df8a6ITaskStore.cs,InMemoryTaskStore.cs, atomic subscribe4cf8a59+f0bd87eTaskCreatedCountfix,ApplyEventAsyncrename752caf0CancelTaskRequest.cse1dd2a8A2AServer.GetTaskAsyncbba4e39RequestContext.cs4602462a20e52e+b9565feClientProvidedContextIdflag69a9030a65992cMessageResponder.cs, sample simplificationMajor Changes
1. A2A v1 Spec Models (
d223872)ContentCase/PayloadCasecomputed enums for alloneoftypesListTaskswith pagination, sorting, filteringMapHttpA2A) with 11 endpoints and SSE streamingA2A-Versionheader negotiation to JSON-RPC processorA2A.V0_3backward compatibility project (65 files, 255 tests)docs/migration-guide-v1.md(12 sections, 21 breaking changes documented)2. Security Hardening (
9e11c78)Exception.Messagein 5 HTTP/JSON-RPC error responses to prevent leaking internal detailsJsonException.MessageinDeserializeAndValidate3. Target Framework Update (
0ebe07a)net9.0;net8.0;netstandard2.0→net10.0;net8.0across all projects#if NETconditional compilation blocks4. Server Architecture Redesign (
ad6e513)BREAKING:
ITaskManager/TaskManager→IA2ARequestHandler/A2AServerA2AServer: Lifecycle orchestrator with context resolution, terminal state guards, event persistence, history managementIAgentHandler: Easy-path agent contract (ExecuteAsync+ defaultCancelAsync)RequestContext: Pre-resolved request envelope (renamed fromAgentContext)AgentEventQueue: Channel-backed event stream with typedEnqueue*methodsTaskUpdater: Lifecycle convenience helper for all 8 task statesMessageResponder: Reply convenience helper — auto-setsRole.Agent,MessageId,ContextIdA2ADiagnostics:ActivitySource('A2A')+Meter('A2A')with 9 metric instrumentsAddA2AAgent<T>()one-line registration +MapA2A(path)endpoint mapping5. Simple CRUD Task Store (
a7df8a6)Replace event sourcing with a 4-method
ITaskStoreinterface matching the Python SDK pattern:SemaphoreSliminChannelEventNotifierguarantees no events missed between task snapshot and channel registrationInMemoryTaskStore:ConcurrentDictionary<string, AgentTask>with deep cloneFileTaskStore(sample): File-backed store with task JSON files + context indexes6. Spec Compliance Fixes
752caf0): AddedMetadataproperty per spec proto field 3e1dd2a8): Reject negative values withInvalidParamsb9565fe): Agents always generate contextId per spec §3.4.1;ClientProvidedContextIdflag lets agents distinguish client-provided vs SDK-generatedbba4e39): Renamed fromIsStreamingto avoid terminology conflict with v1.1 streaming requests7. MessageResponder Helper (
a65992c)Store Architecture Evolution
IEventStore/ITaskEventStorewith 7 methodsITaskStore(4 methods). Subscribe race condition solved via atomic per-task locking.Bug Fixes
9979994): Re-fetch task from store after consuming all events4cf8a59): Only counts genuinely new tasksf0bd87e): Footgun — renamed toApplyEventAsyncad6e513): Run handler concurrently with consumerb9565fe): Always generate contextId when client doesn't provide oneBreaking Changes
ITaskManager→IA2ARequestHandlerad6e513AddA2AAgent<T>()DI helperAgentContext→RequestContext4602462ITaskStore(new interface)a7df8a60ebe07aIsStreaming→StreamingResponsebba4e39AutoPersistEventsremovedf0bd87eValidation
dotnet builddotnet test(net8.0 + net10.0)